#!/usr/bin/python3
# -*- encoding: UTF-8 -*-
# from cmath import log
"""
2023-08-05: 去除L7,L8相关逻辑，当发现有持仓不存在时，直接从数据库查询（此为第一步，只提供相关价格信息，
后面需重构数据获取过程，使得底层算法的逻辑一致，先不去除L7,L8,而是增加对附加数据的加载，等这些附加数据融入进去后再去除相应的逻辑
"""
import copy
import json
import os
import re
import string
import sys
import time
from datetime import datetime
import traceback
import pymysql
import pymysql.cursors
import redis
import requests
from typing import Dict, List, Tuple

from kds_util.user_logbook import init_logger as init_logs
from kds_util.user_logbook import system_log as logger
from kds_util.user_logbook import user_log as user_logger
from kds_util.mysql_database import PyMySql
from base.breed_range import BreedRange
from base import base_util
from typing import Union, Callable
from highlow.envs import Envs


class xjconfig:
    _instance = None

    config = {
        "all_acconts": {
            "moni153": {
                "investor_id": "moni153",
                "url": "http://127.0.0.1:8088/api/mgr",
            },
        },
        "headers": {'Content-Type': 'application/json;charset=UTF-8', 'Connection': 'keep-alive'},
        "investor_id": "moni153",
        "kline_url": "http://127.0.0.1:8086",
        "kline_bak_url": "http://127.0.0.1:8086",
        "all_acconts_url": "http://127.0.0.1:8088",
        "dbIP": "127.0.0.1"
    }

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(xjconfig, cls).__new__(cls)
        return cls._instance

    def __init__(self) -> None:
        self._load_config_from_file()

    def _load_config_from_file(self):
        if os.path.exists("etc/config.json"):
            with open("etc/config.json", "r") as f:
                self.config.update(json.load(f))

    def update_ip(self, new_ip):
        def update_dict_ip(d):
            for k, v in d.items():
                if isinstance(v, str) and re.search(r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b', v):
                    d[k] = re.sub(r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b', new_ip, v)
                elif isinstance(v, dict):
                    update_dict_ip(v)

        update_dict_ip(self.config)

def cfg():
    return xjconfig().config


def sign(a): return 0 if not a else 1 if a > 0 else -1  # 求一个数的符号


def avlist(l): return (sum(l) / len(l)) if l else 0  # 求list中数值的平均数


class TimeProp():
    def __init__(self, ts: Union[int, str] = 0) -> None:
        if type(ts) == str:
            if ts:
                self.time = datetime.strptime(ts, '%Y-%m-%d %H:%M:%S').timestamp()
            else:
                self.time = 0
        else:
            # 小数字, 返回当前时间前后ts秒 # 1000000000 = 2001-09-09 09:46:40 = 31.7年
            self.time = ts if ts >= 1000000000 else time.time() + ts

    @property
    def intTradeDay(self):  # 根据时标获取交易日
        tm = datetime.fromtimestamp(self.time + 14400)  # 加4个小时,判断日期 4*3600
        if tm.weekday() > 4: tm = datetime.fromtimestamp(
            self.time + 14400 + (7 - tm.weekday()) * 86400)  # 周六是5 周日是6 周五夜盘,加完是周六,需要再加多两天 (48+4)*3600
        return int(tm.strftime("%Y%m%d"))

    @property
    def intTradeTime(self):  # 根据时标获取交易时间 int
        return int(self.dt.strftime("%H%M%S"))

    @property
    def intSeg(self):  # 0:夜盘 1:上午盘1 2:上午盘2 3:下午盘
        tt = self.intTradeTime
        if 80000 <= tt < 102000: return 1
        if 102000 <= tt < 120000: return 2
        if 120000 <= tt < 200000: return 3
        return 0

    @property
    def trade_day(self):  # 根据时标获取交易日
        return self.intTradeDay

    @property
    def dt(self):
        return datetime.fromtimestamp(self.time)

    @property
    def trade_date(self):
        return self.dt.strftime("%Y-%m-%d")

    @property
    def trade_time(self):
        return self.dt.strftime("%Y-%m-%d %H:%M:%S")

    def set_time(self, ts: Union[int, str]):
        if type(ts) == str:
            self.time = datetime.strptime(ts, '%Y-%m-%d %H:%M:%S').timestamp()
        else:
            # 小数字, 返回当前时间前后ts秒 # 1000000000 = 2001-09-09 09:46:40 = 31.7年
            self.time = ts if ts >= 1000000000 else time.time() + ts

    def add_seconds(self, seconds: int):
        self.time += seconds

    def __str__(self):
        return self.trade_time

    def __repr__(self):
        return self.__str__()

    def __lt__(self, other):
        if isinstance(other, TimeProp):
            return self.time < other.time
        return NotImplemented

    def __eq__(self, other):
        if isinstance(other, TimeProp):
            return self.time == other.time
        return NotImplemented


class KLine(TimeProp):
    def __init__(self, ktime=0, open=0.0, high=0.0, low=0.0, close=0.0, volume=0, oi=0, instrument="", period=1,
                 date_k=0):
        self.time: int = ktime
        self.open: float = open
        self.close: float = close
        self.high: float = high
        self.low: float = low
        self.volume: int = volume
        self.oi: int = oi
        self.instrument: str = instrument  # 合约号
        self.period: int = period
        self.signal: int = 0
        self.date_k: int = date_k

    def appendkline(self, kline):  # 合并k线，首条k线需要把time清零
        if self.time == 0:
            period = self.period
            self.__dict__.update(kline.__dict__)
            self.period = period
        elif self.time < kline.time:
            self.time = kline.time
            if self.high < kline.high: self.high = kline.high
            if self.low > kline.low: self.low = kline.low
            self.close = kline.close
            self.oi = kline.oi
            self.volume += kline.volume

    def __str__(self):
        return f"[{self.date_k:03d}],[{self.trade_time}],i:{self.instrument}o:{self.open},h:{self.high},l:{self.low}," \
               f"c:{self.close},oi:{self.oi},p:{self.period}"

    def __repr__(self):
        return self.__str__()


def loadklines(codeL, limit=60000, s_time="", e_time="", period="1m"):  # 通过接口获取k线数据，增加基础计算
    klines: List[KLine] = []
    if not codeL: return klines

    tf = int(period[:-1]) if period[-1] == "m" else 1440  # 只支持1分钟和日线

    if e_time == "": e_time = TimeProp().trade_time
    etimestamp = datetime.strptime(e_time, '%Y-%m-%d %H:%M:%S').timestamp()
    if s_time == "1970-01-01 08:00:00":
        s_time = ""
    if s_time != "":
        stimestamp = datetime.strptime(s_time, '%Y-%m-%d %H:%M:%S').timestamp()
        if etimestamp - stimestamp < 10: return klines
    # e_time = TimeProp(int(etimestamp)+30).trade_time
    s_time_str = f"and `datetime`>'{s_time}'" if s_time != "" else ""
    sql = ""
    database = "qh_index_db"
    if codeL[-2:] == "L6":  # 数据库获取
        sql = f"SELECT *,UNIX_TIMESTAMP(datetime) as time FROM  tbl_code_{codeL} where period={tf} {s_time_str} and `datetime`<='{e_time}' order by `datetime` desc limit {limit}"
    elif codeL[-2:].isdigit() and tf != 1440:  # 是具体合约且不是日线的才能在这个表中取到。
        database = "kline"
        breed = codeL.rstrip(string.digits)
        sql = f"SELECT *,UNIX_TIMESTAMP(datetime) as time FROM  tbl_code_{breed}_all where period={tf} and code='{codeL}' {s_time_str} and `datetime`<='{e_time}' order by `datetime` desc limit {limit}"
    elif codeL[-2:] == "L9":  # L9
        database = "hqdb"
        breed = codeL[:-2]
        s_time_str = f"and trading_day>={TimeProp(stimestamp).intTradeDay}" if s_time != "" else ""
        sql = f"SELECT *,UNIX_TIMESTAMP(STR_TO_DATE(CONVERT(trading_day*10000+1500,char),'%Y%m%d%H%i')) as `time` FROM tbl_future_all_kline_day where breed='{breed}' {s_time_str} and trading_day<{TimeProp(int(etimestamp) + 30).intTradeDay} ORDER BY trading_day desc"
    elif codeL[-2:].isdigit() and tf == 1440:   # 是具体合约且是日线的才能在这个表中取
        database = "hqdb"
        s_time_str = f"and trading_day>={TimeProp(stimestamp).intTradeDay}" if s_time != "" else ""
        sql = f"SELECT *,UNIX_TIMESTAMP(STR_TO_DATE(CONVERT(trading_day*10000+1500,char),'%Y%m%d%H%i')) as `time` " \
              f"FROM tbl_future_all_kline_day where code='{codeL}' {s_time_str} and trading_day<{TimeProp(int(etimestamp) + 30).intTradeDay} ORDER BY trading_day desc"
    else:  # 数据库获取 板块
        sql = f"SELECT *,UNIX_TIMESTAMP(datetime) as time FROM  tbl_bk_{codeL} where period={tf} {s_time_str} and `datetime`<='{e_time}' order by `datetime` desc limit {limit}"

    if sql:
        db = pymysql.connect(user="root", password=f"{Envs.dest_mysql_passwd}", host=Envs.dest_ip, database=database,
                             cursorclass=pymysql.cursors.DictCursor)
        cursor = db.cursor()
        try: 
            cursor.execute(sql)
        except Exception as exception:
            logger.error(str(exception))
            return []
                
        result = list(cursor.fetchall())
        db.close()
        result.reverse()
        for index, d in enumerate(result):
            kline = KLine(d['time'], float(d['open']), float(d['high']), float(d['low']), float(d['close']),
                          int(d['volume']), int(d['close_oi']), d['code'], date_k=index + 1)
            # kline.__Dict__.update(d)
            kline.period = tf
            klines.append(kline)

    if len(klines) > 0: logger.info(
        f"Load [{klines[0].trade_time}]->[{klines[-1].trade_time}]{len(klines):4d} {period} klines from {codeL}")
    return klines


# 获取所有账号信息，每分钟刷新一次
def getallacconts():
    allacconts: Dict[str, Dict] = {}
    url = cfg().get("all_acconts_url")
    if url:
        headers = {'Content-Type': 'application/json;charset=UTF-8', 'Connection': 'keep-alive'}
        r = requests.request('GET', url + '/api/mgr/allaccounts', headers=headers)
        if r.status_code == 200: allacconts: Dict[str, Dict] = r.json()["result"]
    else:
        allacconts: Dict[str, Dict] = cfg().get("all_acconts", {})
    return allacconts


class TimeLineClass:
    CurTime: str = "16:36:31"
    TradingDay = 20230321
    UpdateTime: str = "15:16:34"
    PreSettlementPrice = 5167.000
    LastPrice = 5178.000
    BidPrice1 = 5178.000
    BidVolume1 = 7
    AskPrice1 = 5181.000
    AskVolume1 = 1
    SettlementPrice = 5170.000
    PreClose = 5194.000
    open = 5161.000
    high = 5218.000
    low = 5123.000
    close = 5178.000
    volume = 12755
    amount = 989250060.000
    OpenInterest = 24531.0
    PreOpenInterest = 24733.0
    UpperLimitPrice = 5683.000
    LowerLimitPrice = 4650.000
    MinDgDay = 20230321
    MaxDgDay = 20230321

    def __init__(self, rds, symbol) -> None:
        if symbol == '': return logger.error(f"无合约参数")
        str_data = rds.hget('future_realtime', symbol)
        if not str_data: return logger.error(f"{symbol}:无合约数据")
        str_data = re.sub("(\w+)=", '"\g<1>"=', str_data)
        str_data = re.sub("(\d+:\d+:\d+)", '"\g<1>"', str_data)
        self.__dict__.update(json.loads("{" + str_data.replace("=", ":") + "}"))


# 通过接口获取一个账户信息
class Accont(TimeProp):
    def __init__(self, investor_id: str = "", url=""):
        self.initEquity: float = 1e7  # 初始权益
        self.investor_id: str = investor_id
        self.url: str = url
        self.ip: str = base_util.parse_ip_from_url(self.url)
        xjconfig().update_ip(self.ip)
        self.Contracts: List[str] = ['']
        self.Codes: List[str] = ['']
        self.dtqy: float = 0.0  # http动态权益  未结算权益+浮动盈亏
        self.sxf: float = 0.0  # http手续费    已发生手续费
        self.kyzj: float = 0.0  # http可用资金  动态权益-保证金
        self.time = time.time()
        # self.rds: redis.Redis = None
        self.Holds: Dict[str, Hold] = {}
        self.OldHolds: Dict[str, Hold] = {}  # 旧持仓，用来判断持仓是否发生变化
        self.HoldChange: bool = False  # 持仓发生变化的标识

        acc: Dict = {}
        acc['investor_id'] = investor_id
        acc['url'] = url
        self.__dict__.update(acc)

        self.rds = redis.Redis(self.ip, decode_responses=True)
        try:
            r = requests.request('GET', f"{self.url}/oneaccountinfo?investor_id={self.investor_id}",
                                 headers=cfg()["headers"], timeout=2)
        except:
            logger.critical(f"Access {self.url}/oneaccountinfo?investor_id={self.investor_id} Error!");
            raise Exception(f"Access {self.url}/oneaccountinfo?investor_id={self.investor_id} Error!");

        self.updatetime = time.time()
        if r.status_code == 200: self.__dict__.update(r.json()["result"])  # 通过接口获取一个账户信息

    def qhaccountdata(self, tradingday=0):
        sql = f"SELECT TradingDay,CurBalance,Available,Commission from tbl_future_trading_account WHERE InvestorID='{self.investor_id}' and TradingDay={tradingday};"
        if tradingday == 0: sql = f"SELECT TradingDay,CurBalance,Available,Commission from tbl_future_trading_account WHERE InvestorID='{self.investor_id}' order by TradingDay desc limit 1"

        db = pymysql.connect(user="root", password=f"{Envs.dest_mysql_passwd}", host=self.ip, database="hqdb",
                             cursorclass=pymysql.cursors.DictCursor)
        cursor = db.cursor()
        cursor.execute(sql)
        result = cursor.fetchall()
        cursor.close()
        if not result: return
        self.dtqy = float(result[0]["CurBalance"])
        self.initEquity = self.dtqy
        self.kyzj = float(result[0]["Available"])
        self.sxf = float(result[0]["Commission"])

    def qhholdlist(self, tradingday=0):  # 还原某天持仓,缺省当天
        self.qhaccountdata(tradingday)
        if tradingday == 0: tradingday = TimeProp().intTradeDay
        db = pymysql.connect(user="root", password=f"{Envs.dest_mysql_passwd}", host=self.ip, database="hqdb",
                             cursorclass=pymysql.cursors.DictCursor)
        cursor = db.cursor()
        cursor.execute(
            f"SELECT * from tbl_future_position_general WHERE InvestorID='{self.investor_id}' and TradingDay={tradingday}")
        result = list(cursor.fetchall())
        cursor.close()
        self.Holds.clear()
        for row in result:
            open_price = float(row["OpenAmount"]) / float(row["OpenVolume"]) / float(row["VolumeMultiple"]) if float(
                row["OpenVolume"]) else float(row["LastPrice"])
            self.Holds[f'{row["InstrumentID"]}.{row["Direction"]}'] = Hold({
                "InstrumentID": row["InstrumentID"],
                "Direction": row["Direction"],
                "UseMargin": float(row["UseMargin"]),
                "Position": int(row["Position"]),
                "PositionProfit": float(row["PositionProfit"]),
                "open_price": float(open_price),
                "Commission": float(row["Commission"]),
                "PositionProfitByTrade": float(row["PositionProfitByTrade"]),
                "CloseProfit": float(row["CloseProfit"]),
                "LastPrice": float(row["LastPrice"])})
        if self.Holds == self.OldHolds:
            self.HoldChange = False
        else:
            self.HoldChange = True
        self.OldHolds = copy.deepcopy(self.Holds)
        return

    def TimeLine(self, symbol: str) -> TimeLineClass:
        return TimeLineClass(self.rds, symbol)


class Order():
    def __init__(self, instrument_id="", effect_type='0', posi_direction='2', price=0.0, volume=1, trade_time=0,
                 price_type="high_low_price", condition_type="1") -> None:
        self.price_type: str = price_type  # "high_low_price":最高或最低价成交,此种类型，不需要"price"参数，"limit"：限价成交,同时需要提供price参数
        self.condition_type: str = condition_type  # "1": 立即完成，否则撤销（指令单), "3": 当日有效, 可选项，若不存在或不为"1"表示为当日有效的指令单
        self.effect_type: str = effect_type  # "0": 开仓， "1": 平仓
        self.posi_direction: str = posi_direction  # "2": 多头， "3":空头
        self.open_price: float = price
        self.volume: int = volume
        self.instrument_id: str = instrument_id
        self.open_time = trade_time if trade_time != 0 else TimeProp().time
        self.msg = ""


class KlineList(TimeProp):
    def __init__(self, tf: int = 1) -> None:
        self.time = 0
        self.klines: List[KLine] = []
        self._id = 0
        self.tf = tf
        self.tid = 0
        self.newkline = KLine()

    def input(self, kline1m: KLine, intCloseTime: int, breed=None):
        if breed in BreedRange.arr_total:
            self.tid += 1
            try:
                dg_time = BreedRange.period_range[self.tf][breed][kline1m.intTradeTime]
            except Exception as e:
                print(e)
            self.newkline.appendkline(kline1m)
            if dg_time == kline1m.intTradeTime:  # 表示结束
                self.klines.append(self.newkline)
                self.newkline = KLine()
                self.tid = 0
        else:
            self.tid += 1
            if kline1m.intTradeTime == intCloseTime: self.tid = self.tf  # 收盘时间
            if kline1m.time - self.newkline.time > 200 and self.tf - self.tid < 4:  # 时间有较大跨度# 合并快完成，说明有丢失k线
                self.newkline.time = (self.newkline.time + 299) // 300 * 300  # 往后对齐到5分钟边界
                self.klines.append(self.newkline)
                self.newkline = KLine()
                self.tid = 1
            self.newkline.appendkline(kline1m)
            if self.tid == self.tf:
                self.klines.append(self.newkline)
                self.newkline = KLine()
                self.tid = 0

    @property
    def last_kline(self) -> KLine:
        if not self.klines: return KLine()
        if self._id >= len(self.klines):
            self._id = len(self.klines)
            return self.prev_kline()
        kline = self.klines[self._id]
        self.time = kline.time
        return kline

    def move_next(self, tm: int = 0):
        if self.last_time == tm or tm == 0: self._id += 1

    @property
    def last_time(self) -> int:
        return self.last_kline.time

    @property
    def id(self) -> int:
        if self._id >= len(self.klines): return len(self.klines) - 1
        return self._id

    def have_kline(self, tm) -> int:
        if self._id < len(self.klines) and self.last_time <= tm: return self.last_time
        return 0

    def last_n_klines(self, N) -> List[KLine]:
        return self.klines[:self._id + 1][-N:]

    def prev_kline(self, N=-1) -> KLine:  # 上一根k线
        if self.klines and self._id + N >= 0: return self.klines[self._id + N]
        return self.last_kline

    def __str__(self):
        return f"[{self.trade_time}],index=[{self.id}],size={len(self.klines)}"

    def __repr__(self):
        return self.__str__()

# 合约/指数/板块
class Contract(TimeProp):
    def __init__(self, acc, codeL="", code="") -> None:
        # self.investor_id="moni156"
        self.time = 0
        self.codeL: str = codeL  # agL6,agL9,guijinshu
        self.breed: str = ""  # ag,ag,""
        self.typ: str = ""  # "L9","L6","BK","Hold" 表示的为持仓具体合约，且不在主力合约中
        self.code: str = code  # OI209,"",""
        self.ExchangeId: str = ""  # CZCE
        self.InstrumentName: str = ""  # 白银2012
        self.Ma20Turnover: float = 0
        self.OpenRatioByVolume: float = 0
        self.CloseTodayRatioByVolume: float = 0
        self.CloseRatioByVolume: float = 0
        self.OpenRatioByMoney: float = 0.0013
        self.CloseTodayRatioByMoney: float = 0.0003
        self.CloseRatioByMoney: float = 0.0003
        self.MarginRatioByMoney: float = 1
        self.Multiplier: float = 100
        self.Minunit: float = 0.01
        self.klines1m: KlineList = KlineList()  # 分钟k线
        self.klines5m: KlineList = KlineList(5)  # 5分钟k线
        self.klines15m: KlineList = KlineList(15)  # 15分钟k线
        self.klines30m: KlineList = KlineList(30)  # 加1个30分钟K线
        self.klines60m: KlineList = KlineList(60)  # 60分钟k线
        self.klines120m: KlineList = KlineList(120)  # 120分钟k线
        self.klines1d: KlineList = KlineList(1440)  # 日线
        self.orders: List[Order] = []  # 本合约开单, 建议L9数据才开仓
        self.Disable: bool = False  # True:盘中不刷新1分钟数据. 需要刷新,需要置成False
        self.signal = 0
        self.period = 0
        self.acc = acc
        self.win = 0
        self.lose = 0
        self.win_m = 0
        self.lose_m = 0
        self.market = 0
        self.attach: Dict[str, Contract] = {}  # 附加的，针对持仓的非主力合约的情况下生成

        if len(codeL) < 3: return
        self.codeL = codeL
        self.typ = codeL[-2:]
        if self.typ[-2] == "L":  # 指数,不需要合约交易数据
            self.breed = codeL[:-2]
            if self.typ == "L9":  # 主连, 读取对应合约交易数据
                r = requests.request('GET',
                                     "%s%s?investor_id=%s&code=%s" % (acc.url, '/accountcode', acc.investor_id, code),
                                     headers={'Content-Type': 'application/json;charset=UTF-8'})
                if r.status_code == 200: self.__dict__.update(r.json()["result"])
        elif codeL[-3:].isdigit():
            self.typ = "Hold"  # 持仓具体合约
            self.breed = codeL.rstrip(string.digits)
            r = requests.request('GET',
                                 "%s%s?investor_id=%s&code=%s" % (acc.url, '/accountcode', acc.investor_id, code),
                                 headers={'Content-Type': 'application/json;charset=UTF-8'})
            if r.status_code == 200: self.__dict__.update(r.json()["result"])
        else:
            self.typ = "BK"

    def __str__(self):
        return f"[{self.typ}.{self.code}]"

    def __repr__(self):
        return self.__str__()

    def load_day_klines(self, stime: str = "", etime: str = "") -> int:
        self.klines1d.klines = loadklines(self.codeL, 1000, stime, etime, '1d')  # 读入日线数据,固定最多500日
        return len(self.klines1d.klines)

    # 跑日线之前，调用
    def load_minute_klines(self, stime="", etime="") -> int:
        if self.typ == "BK" or self.typ == "L6": self.code = self.codeL
        klines = loadklines(self.code, 1440, stime, etime, '1m')  # 读入分钟线数据
        for k in klines:  # 合并k线
            for kx in [self.klines5m, self.klines15m, self.klines30m, self.klines60m, self.klines120m]:
                kx.input(k, self.intCloseTime, self.breed)
        if etime:  # 首次调用
            self.klines1m.klines = klines
            self.klines1m._id = 0
        else:
            self.klines1m.klines += klines  # 实盘刷新
        return len(klines)

    @property
    def intCloseTime(self):
        return 151500 if self.breed in ["T", "TS", "TF"] else 150000

    @property
    def Last_Day_Kline(self):
        return self.klines1d.last_kline

    @property
    def Last_minute_Kline(self):
        if self.klines1m.last_kline.time: return self.klines1m.last_kline
        return self.klines1d.last_kline

    @property
    def LastPrice(self):
        return self.Last_minute_Kline.close

    def calorder(self, o, price, v) -> Tuple[float, float, float]:  # 假设price价格平仓v手,计算盈亏
        d: int = 1 if o.posi_direction == "2" else -1
        Margin: float = v * price * self.Multiplier * self.MarginRatioByMoney  # 保证金
        Earn: float = v * d * (price - o.open_price) * self.Multiplier  # 浮动盈亏
        Fee: float = v * (
                o.open_price * self.Multiplier * self.CloseTodayRatioByMoney + self.CloseTodayRatioByVolume)  # 平仓手续费
        return Margin, Earn, Fee

    @property
    def long_position(self):  # 不区分合约
        return sum([o.volume for o in self.orders if o.posi_direction == "2"])

    @property
    def short_position(self):  # 不区分合约
        return sum([o.volume for o in self.orders if o.posi_direction == "3"])

    @property
    def net_position(self):  # 关联持仓 不区分合约
        return sum([o.volume * [1, -1][o.posi_direction == "3"] for o in self.orders])

    @property
    def one_margin(self):
        return self.LastPrice * self.Multiplier * self.MarginRatioByMoney


class Hold():
    def __init__(self, hold: Dict) -> None:
        self.InstrumentID = ""
        self.Direction = ""  # 方向
        self.Position = 0  # 持仓手数
        self.UseMargin = 0.0
        self.PositionProfit = 0.0
        self.PositionProfitByTrade = 0.0
        self.CloseProfit = 0.0
        self.Commission = 0.0  # 手续费
        self.open_price = 0.0  # 开仓价
        self.LastPrice = 0.0  # 最新价
        self.__dict__.update(hold)

    def __eq__(self, o):  # 重写等于判断，比较合约Id，方向，持仓量
        if isinstance(o, Hold):
            return self.InstrumentID == o.InstrumentID and self.Direction == o.Direction and \
                self.Position == o.Position
        return False

    def refresh_profit(self, m: Contract, price, close_price):
        d = 1 if self.Direction == "2" else -1
        self.UseMargin = self.Position * price * m.Multiplier * m.MarginRatioByMoney
        self.PositionProfit = self.Position * d * (close_price - self.open_price) * m.Multiplier
        self.PositionProfitByTrade = self.Position * d * (price - self.open_price) * m.Multiplier
        self.LastPrice = price

    def __str__(self):
        return f"[{self.Direction}.{self.InstrumentID}],v:{self.Position}," \
               f"price:{self.LastPrice},Commission:{self.Commission}," \
               f"OpenPrice:{self.open_price},UseMargin:{self.UseMargin}"

    def __repr__(self):
        return self.__str__()


def GetBkTable() -> Tuple[Dict[str, str]]:
    db = pymysql.connect(user="root", password=Envs.dest_mysql_passwd, host=cfg()["dbIP"], database="qh_index_db",
                         cursorclass=pymysql.cursors.DictCursor)
    cursor = db.cursor()
    cursor.execute(
        "SELECT BkCode,BkName,ContractWeightCode,ContractWeightName,BkType,Filter FROM  tbl_component_bk where market=0")  # where `Filter`='要'
    result: Tuple[Dict[str, Dict[str, str]]] = cursor.fetchall()
    db.close()
    return result


class kBase(Accont):
    def __init__(self, investor_id="", days=100, mindays=1, stime="", etime="", codes=[], playback=False,
                 turnover_threshod=1e8, url="", funcs: Callable = None):  #
        # stime 开始时间，对齐到交易日,空表示当前时间,用于实盘/模拟盘,非空用于回放
        # etime 结束时间
        # days stime之前的日线数量
        # mindays stime之前的分钟线天数
        super().__init__(investor_id, url)
        self.allContract: Dict[str, Contract] = {}  # 所有个票数据，key是品种+L9/L6/板块，value是Class Contract
        self.Breed2Symbol: Dict[str, str] = {}  # 所有品种数据，key是品种，value是主力合约。
        self.bkinfo: Dict[str, List[str]] = {}  # key是板块名，value是板块关联品种列表
        self.RealCount: int = 0  # 实盘计数器,模拟状态为0,实盘每过一分钟加一
        self.playback = playback  # 是否是回放 True:回放 False:实盘 默认是实盘
        self.pre_dtqy = 0
        self.turnover_threshod = turnover_threshod
        self.main_contract_info = {}
        self.data_center_ip = cfg()["dbIP"]
        self.data_center_url = cfg().get("all_acconts_url")
        self.status = 0  # 状态 0:启动 10000:运行日线+天数 20000:运行预置分钟线+天数 30000:运行分钟线+天数 40000:实盘运行+刷新k线次数
        self.win = 0
        self.lose = 0
        self.win_m = 0
        self.lose_m = 0
        self.call_back_update_usr_param = funcs

        if mindays >= days: logger.critical(f"mindays={mindays} 不能大于 days={days}")
        if mindays <= 0: mindays = 1  # 保护传入分钟数为0的情况
        self.stime = stime
        self.etime = etime
        if self.etime == "": self.etime = TimeProp().trade_time
        if self.stime == "": self.stime = TimeProp().trade_time
        self.stime = TimeProp(self.stime).trade_time
        self.etime = TimeProp(self.etime).trade_time

        agL6 = loadklines("agL6", days, "", self.stime, "1d")

        self.sdate: str = agL6[0].trade_time  # 日线开始日期
        self.minute_start_time: str = TimeProp(agL6[-mindays].time + 3600).trade_time  # 分钟回放时间

        user_logger.info(
            f"初始化：investor_id={investor_id} days={days} mindays={mindays} stime={self.stime} etime={self.etime} "
            f"codes={codes} playback={playback} minute_start_time={TimeProp(self.minute_start_time).trade_time}")

        self.load_main_contract_tbl(self.sdate, self.etime)  # 获取主力合约次主力合约信息，并分配日k线

        # 个票
        for i in range(len(self.Contracts)):
            breed = self.Codes[i]
            if codes and breed not in codes: continue
            self.Breed2Symbol[breed] = self.Contracts[i]
            self.allContract[breed + "L9"] = self.gen_new_contract(breed + "L9", self.Contracts[i])
            self.allContract[breed + "L6"] = self.gen_new_contract(breed + "L6", "")
            # if self.allContract[breed + "L9"].Ma20Turnover > self.turnover_threshod:
            self.allContract[breed + "L9"].load_day_klines(self.sdate, self.etime)
            self.distrabute_kline(self.allContract[breed + "L9"])
            self.allContract[breed + "L6"].load_day_klines(self.sdate, self.etime)

        user_logger.info(f"初始化完成：investor_id={investor_id} initEquity={self.initEquity}")

    def gen_new_contract(self, codeL="", code=""):
        user_logger.info(f"new_contract: codeL:[{codeL}], code:[{code}]")
        return Contract(self, codeL, code)

    def L9(self, code: str) -> Contract:
        l9_code = code.replace("L6", "").rstrip(string.digits) + "L9"
        if l9_code in self.allContract:
            return self.allContract[l9_code]
        else:
            logger.warn(f"异常发生，找不到{l9_code}, 将生成对应的L9,但没有相应数据")
            self.allContract[l9_code] = Contract(self, l9_code)
            return Contract(self, l9_code)

    def GetOrNewContract(self, code) -> Contract:
        L9 = self.L9(code)
        if L9.code == code: return L9
        if code in L9.attach:
            return L9.attach[code]
        L9.attach[code] = self.gen_new_contract(code, code)
        self.allContract[code] = L9.attach[code]
        m = self.allContract[code]
        m.load_day_klines(self.sdate, self.etime)
        self.call_back_update_usr_param(code)  # 调用以实现DDParams的更新
        self.re_run(m, self.time)  # 完成指标状态更新
        return m

    def GetAccontInfo(self, tradingday=0):  # 实盘查询仓位, 主要在子线程调用,增加回调处理
        self.qhholdlist(tradingday)
        # 还原订单
        for m in list(self.allContract.values()):  # TODO: 这儿还原订单问题，还需深入 by luhx 2023-08-04
            m.orders.clear()
        # holds = copy.deepcopy(self.Holds)
        for hold in self.Holds.values():
            jc = hold.Position
            if jc == 0:
                continue
            # m = self.LL(hold.InstrumentID)  # TODO: 这儿还原订单问题，还需深入 by luhx 2023-08-04
            m = self.GetOrNewContract(hold.InstrumentID)
            if m:  # 反向还原订单
                m.orders.append(Order(hold.InstrumentID, '0', hold.Direction, float(hold.open_price),
                                      jc))  # 根据持仓,补充订单,全部按照今仓? 不影响算法
            else:
                logger.warning(f"{hold.InstrumentID}在{self.url}上面有持仓,不在列表中,可能是非主力合约,需要手动处理")
        self.log_hold_list()

    def load_main_contract_tbl(self, stime: str = "1970-1-1", etime: str = ""):
        db = pymysql.connect(user="root", password=Envs.dest_mysql_passwd, host=self.data_center_ip, database="hqdb",
                             cursorclass=pymysql.cursors.DictCursor)
        cursor = db.cursor()
        cursor.execute(f"select * from tbl_factor_main_contract where ktime>='{stime}' and ktime<='{etime}'")
        data = cursor.fetchall()
        for row in data:
            self.main_contract_info[TimeProp(row["ktime"].timestamp()).intTradeDay] = json.loads(row["info"])
        db.close()

    def distrabute_kline(self, m):
        allk = m.klines1d.klines
        m.klines1d.klines = []
        pinfo = {}
        if m.typ == "L9":
            for k in allk:
                info = pinfo = self.main_contract_info.get(k.intTradeDay, pinfo)
                if not info: continue
                if k.instrument == info[m.breed][0]: m.klines1d.klines.append(k)

    def orderbuysell(self, o=Order()):  # 实盘模拟盘下单
        try:
            logger.info(
                f"http:{self.investor_id}:{o.instrument_id} {o.open_price:8.3f} vol:{o.volume:3.0f} dir:{o.posi_direction} {o.effect_type}")
            url: str = self.url.replace("8088", "8084").replace("mgr", "virtual") + '/orderbuysell'
            param: Dict[str, str] = {
                "admin_id": "100000",
                "token": "jiang",
                "user": "jiang",
                "account_id": self.investor_id,
                "account_type": "virtual",
            }
            param.update(o.__dict__)

            requests.request('POST', url, json=param,
                             headers={'Content-Type': 'application/json;charset=UTF-8', 'Connection': 'keep-alive'})
        except:
            logger.error(f"orderbuysell下单失败 {o.instrument_id}")

    def LastPrice(self, instrument_id):
        m: Contract = self.GetOrNewContract(instrument_id)
        if m: return m.LastPrice
        return self.L9(instrument_id).LastPrice

    def simorderbuysell(self, o: Order):  # 模拟下单
        orders = []
        m: Contract = self.GetOrNewContract(o.instrument_id)  # 只在L9下单,因为L9有合约信息 ,看起来这儿被篡改，需用显示创建，而不能用默认的方式
        if not m: user_logger.info(f"Order error:{o.instrument_id}");return

        hold = self.Holds.get(f"{o.instrument_id}.{o.posi_direction}",
                              Hold({"InstrumentID": o.instrument_id, "Direction": o.posi_direction}))
        self.Holds[f"{o.instrument_id}.{o.posi_direction}"] = hold

        strD = {"02": "\033[1;91m开多\033[0m", "03": "\033[1;92m开空\033[0m", "12": "平多", "13": "平空"}[
            o.effect_type + o.posi_direction]
        vol = o.volume = int(o.volume)
        o.open_price = self.LastPrice(o.instrument_id)  # 这里强制用最新价格下单，不做撮合
        if o.open_price == 0:
            user_logger.error(f"{m.codeL} {o.instrument_id} error![zero price] 下单失败")
            return
        earn = 0
        if o.effect_type == "0":  # 开仓
            hold.Commission += o.volume * (
                    o.open_price * m.Multiplier * m.OpenRatioByMoney + m.OpenRatioByVolume)  # 手续费
            m.orders.append(o)
            if hold.Position + o.volume == 0: return
            hold.open_price = (hold.open_price * hold.Position + o.volume * o.open_price) / (
                    hold.Position + o.volume)  # 计算持仓均价
            hold.Position += o.volume
            orders.append(o)
        else:  # 平仓,忽略合约区别
            for o1 in m.orders:
                if o1.posi_direction != o.posi_direction: continue  # o1.instrument_id != o.instrument_id 忽略合约 区别 不分合约平仓,否则留仓移仓出问题
                hold = self.Holds[f"{o1.instrument_id}.{o1.posi_direction}"]  # 用合约持仓
                v: int = min(o1.volume, o.volume)  # 可平量
                o.open_price = self.GetOrNewContract(o1.instrument_id).LastPrice  # 使用持仓合约的最新价格
                o.volume -= v
                o1.volume -= v
                hold.open_price = (hold.open_price * hold.Position - v * o1.open_price) / (
                        hold.Position - v) if hold.Position - v > 0 else o.open_price  # 计算持仓均价
                e: float = v * [1, -1][o.posi_direction == "3"] * (o.open_price - o1.open_price) * m.Multiplier  # 浮动盈亏
                f: float = v * (
                        o.open_price * m.Multiplier * m.CloseTodayRatioByMoney + m.CloseTodayRatioByVolume)  # 平仓手续费
                hold.Position -= v
                hold.CloseProfit += e
                hold.Commission += f
                earn += e - f
                orders.append(Order(o1.instrument_id, o.effect_type, o.posi_direction, o.open_price, v, 0, o.price_type,
                                    o.condition_type))
                if o.volume <= 0: break
            m.orders = [x for x in m.orders if x.volume != 0]  # 清理掉平完仓的订单
        strearn, strrate = "", ""
        if earn:
            strearn = f"\033[1;91m{earn / 1e4:8.4f}万\033[0m" if earn > 0 else f"\033[1;92m{earn / 1e4:8.4f}万\033[0m"
            if earn < 0:
                self.lose += 1
                self.lose_m -= earn
                m.lose += 1
                m.lose_m -= earn
            else:
                self.win += 1
                self.win_m += earn
                m.win += 1
                m.win_m += earn
            winrate = self.win / (self.win + self.lose) if self.win + self.lose else 0
            winlose = self.win_m / self.lose_m if self.lose_m else 0
            mwinrate = m.win / (m.win + m.lose) if m.win + m.lose else 0
            mwinlose = m.win_m / m.lose_m if m.lose_m else 0
            strrate = f"[{m.win}:{m.lose} {mwinlose:4.2f}][{self.win}:{self.lose} {winlose:4.2f}]"
        user_logger.info(
            f"{self.investor_id}[{self.trade_time}] {strD}\033[48;5;{hash(o.instrument_id) % 220}m{vol:3.0f}手,价{o.open_price:10.2f} {o.instrument_id:7s}\033[0m {strearn} {strrate} {o.msg}")  # 模拟交易日志
        return orders

    def doOrder(self, o):
        # if abs(self.LL(o.instrument_id).Last_minute_Kline.time - self.time) > 3: return  # k线和当前时间不一致
        # if self.time < self.stime: return  # stime之前,不处理订单
        try:
            orders = self.simorderbuysell(copy.deepcopy(o))  # 模拟下单
            if self.RealCount:
                self.orderbuysell(o)  # 直接实盘下单
            else:
                if not self.playback:
                    for o1 in orders:
                        self.orderbuysell(o1)

                # %%主要策略代码，每分钟调用一次
        except Exception as e:
            logger.error(str(e))
        return

    def run_minute_kline(self, m: Contract):
        pass

    def run_5_minute_kline(self, m: Contract):
        pass

    def run_15_minute_kline(self, m: Contract):
        pass

    def run_30_minute_kline(self, m: Contract):
        pass

    def run_60_minute_kline(self, m: Contract):
        pass

    def run_120_minute_kline(self, m: Contract):
        pass

    def run_day_kline(self, m: Contract):
        pass

    def run_minute_kline2(self):
        pass

    def run_day_kline2(self):
        pass

    def run_day2minute(self):  # 分钟线运行前先运行的函数
        pass

    def refresh_profit(self):
        self.dtqy = self.initEquity  # 动态权益
        Margin: float = 0.0  # 保证金
        self.sxf = 0.0
        for h in self.Holds.values():  # 统计所有票的保证金/盈亏/手续费
            m = self.GetOrNewContract(h.InstrumentID)
            if not m:
                continue
            # if not L9.code or not LL: continue  # 找不到L9，或者LL 都不计算
            h.refresh_profit(m, m.LastPrice, m.klines1d.last_kline.close)  # 刷新收益和日内收益
            Margin += h.UseMargin
            self.dtqy += h.CloseProfit
            self.dtqy += h.PositionProfitByTrade
            self.sxf += h.Commission
        self.dtqy -= self.sxf  # 动态权益扣除手续费
        self.kyzj = self.dtqy - Margin  # 可用资金

    def save_networth2db(self):
        if self.pre_dtqy == 0: self.pre_dtqy = self.initEquity
        # if self.pre_dtqy == self.dtqy: return
        user_logger.info(
            f"\033[1;96m{self.investor_id}[{self.intTradeDay}]动态权益 {self.dtqy / 1e4:.4f}万 占用 {(self.dtqy - self.kyzj) / 1e4:.4f}万[{(self.dtqy - self.kyzj) * 100 / self.dtqy:.2f}%]\033[0m")
        db = pymysql.connect(user="root", password=Envs.dest_mysql_passwd, host=self.ip, database="hqdb",
                             cursorclass=pymysql.cursors.DictCursor)
        db.query(f"""replace into tbl_investor_networth(InvestorID,trading_day,original_networth,yes_networth,cur_networth,income_rate,available_cash,`datetime`)
                values('{self.investor_id}',{self.intTradeDay},{self.initEquity},{self.pre_dtqy},{self.dtqy},{self.dtqy / self.initEquity - 1},{self.kyzj},now())""")
        db.commit()
        db.close()
        self.pre_dtqy = self.dtqy

    def log_hold_list(self):
        strhold = f""
        for hold in self.Holds.values():
            if hold.Position == 0: continue
            strhold += f"[{hold.InstrumentID}:{hold.Direction}:{hold.Position}]"
        if not strhold:
            strhold = f"[]"
        if self.HoldChange:
            msg = f"\033[1;95m当前持仓：发生变化：[{self.trade_time}]:{strhold}\033[0m"
            user_logger.info(msg)
        else:
            pass
            # msg = f"当前持仓：保持不变：[{self.trade_time}]:{strhold}"


    def reload_kline(self, m: Contract):
        pass

    def re_run(self, m: Contract, system_time: int):
        """重新跑K线到达指定时间"""
        """这儿应该先加载日线数据"""
        pdtm = ""
        while True:
            dtm: TimeProp = TimeProp(int(system_time + 900))  # 900秒即为15分钟
            dtm: TimeProp = self.get_earliest_day_kline_dt({m.code: m}, dtm)  # 获取最早的日线级的时间
            if dtm.time > TimeProp(system_time).time:  # 日线跑完了
                break
            if dtm.time > TimeProp(self.minute_start_time).time:  # 调入一天分钟线，并运行
                m.load_minute_klines(TimeProp(pdtm).trade_time, TimeProp(dtm.time + 3600).trade_time)
                while True:
                    tm: TimeProp = TimeProp(int(dtm.time + 3600) + 300)  # 初始化一个5分钟后的时间
                    tm: TimeProp = TimeProp(self.get_earliest_minute_kline_time({m.code: m}, tm.time))
                    if tm.time <= TimeProp(dtm.time + 3600).time:  # 有分钟线，就跑   系统时间，稍后移至
                        self.run_one_minute({m.code: m}, tm, refresh_flag=False)
                    else:
                        break
            pdtm = dtm.trade_time
            m.klines1d.move_next()

        user_logger.info(f"re_run: [{m.code}]:1d_len:{len(m.klines1d.klines)},1m_len:{len(m.klines1m.klines)},K线：{m.klines1m.trade_time},系统：{self.trade_time}")
        return

    def runallklines(self):
        BreedRange.init()
        self.time = 0
        pdtm = ""
        user_logger.info(f"开始回放[{self.trade_time}]：investor_id={self.investor_id}")
        self.status = 10000  # 开始回放日线
        try:
            while 1:  # 日线循环
                dtm: TimeProp = TimeProp(int(time.time() + 900))  # 900秒即为15分钟
                dtm: TimeProp = self.get_earliest_day_kline_dt(self.allContract, dtm)  # 获取最早的日线级的时间
                if dtm.time > TimeProp(self.etime).time and self.playback:  # 日线跑完了
                    return
                if dtm.time > TimeProp(self.minute_start_time).time:  # 调入一天分钟线，并运行
                    if self.status < 20000:
                        self.status = 20000
                        self.run_day2minute()  # 日线运行完毕,准备运行分钟线;该函数只运行一次
                    # self.log_hold_list()
                    for m in list(self.allContract.values()):  # 调入分钟线
                        m.load_minute_klines(TimeProp(pdtm).trade_time, TimeProp(dtm.time + 3600).trade_time)

                    while 1:
                        if self.time > TimeProp(self.stime).time and self.status < 30000:
                            self.status = 30000  # 开始回放，开仓
                        tm = TimeProp(int(time.time()) + 300)  # 初始化一个5分钟后的时间
                        tm = TimeProp(self.get_earliest_minute_kline_time(self.allContract, tm.time))

                        if tm.time < time.time():  # 有分钟线，就跑
                            self.time = tm.time  # 新的时刻来临，对齐所有k线
                            self.run_one_minute(self.allContract, tm, refresh_flag=True)

                            Margin = self.dtqy - self.kyzj  # 保证金计算
                            logger.info(
                                f"{self.trade_time}({self.intTradeDay}) 动态权益:{self.dtqy / 10000:.2f}万 保证金:{Margin / 10000:.2f}万({100 * Margin / self.dtqy:.2f}%) 可用资金:{self.kyzj / 10000:.2f}万({100 * self.kyzj / self.dtqy:.2f}%)")
                        else:  # 说明k线走完,进入等待k线
                            if self.intTradeDay < TimeProp().intTradeDay and dtm.time < time.time():
                                break  # 回放没有到达今日，并且还有日线，退出循环
                            if self.playback:
                                return
                            if self.status < 40000: self.status = 40000  # 开始实盘
                            self.real_refresh()
                            self.status += 1
                if dtm.time < time.time():
                    dtm.set_time(int(datetime.strptime(str(TimeProp(dtm.time).intTradeDay * 10000 + 1600),
                                                       '%Y%m%d%H%M').timestamp()))  # 对齐到交易日收盘后16:00
                pdtm = dtm.trade_time
                self.time = dtm.time
                for m in list(self.allContract.values()):
                    m.time = self.time
                    if m.klines1d.intTradeDay == self.intTradeDay:  # 15:00 15:15 日线同时跑
                        self.run_day_kline(m)
                self.run_day_kline2()  # 对齐后，全部调用一次日线
                self.status += 1

                if self.RealCount == 0: self.refresh_profit()  # 回放分支 动态计算,需要修正
                if self.playback and self.dt.timestamp() > TimeProp(self.stime).time:
                    self.save_networth2db()

                for m in list(self.allContract.values()):
                    if m.klines1d.intTradeDay == self.intTradeDay:  # 15:00 15:15 日线同时跑
                        m.klines1d.move_next()
                Margin = self.dtqy - self.kyzj  # 保证金计算
                logger.info(
                    f"{self.trade_time}({self.intTradeDay}) 动态权益:{self.dtqy / 10000:.2f}万 "
                    f"保证金:{Margin / 10000:.2f}万({100 * Margin / self.dtqy:.2f}%) "
                    f"可用资金:{self.kyzj / 10000:.2f}万({100 * self.kyzj / self.dtqy:.2f}%)")
        except Exception:
            user_logger.critical(traceback.format_exc())

    def get_earliest_day_kline_dt(self, contracts: Dict[str, Contract], dtm: TimeProp) -> TimeProp:
        for m in list(contracts.values()):  # 找最老的k线
            if m.market:
                continue  # 先跳过外盘
            if m.typ != "Hold":  # 为具体合约时，不应该再赋值（实际是只要不是L9就不应该再赋值），当前L6,BK默认为空，后面一并改
                m.code = m.Last_Day_Kline.instrument  # 这儿引发的code为空，需先将所有数据处理 TODO: luhx 2023-08-06
            if m.klines1d.have_kline(dtm.time + 3600):  # 在dtm前面有没有k线
                dtm = min(TimeProp(m.klines1d.last_time), dtm)  # 重设时间
        return copy.copy(dtm)

    def get_earliest_minute_kline_time(self, contracts: Dict[str, Contract], tm: int) -> int:
        """找最老的分钟k线"""
        for m in list(contracts.values()):
            if m.klines1m.have_kline(tm):
                tm: int = min(tm, tm if m.klines1m.last_time == 0 else m.klines1m.last_time)
        return int(tm)

    def run_one_minute(self, contracts: Dict[str, Contract], dtm: TimeProp, refresh_flag=False):
        tm = dtm.time
        for m in list(contracts.values()):
            m.time = tm
            if m.klines1m.last_time == tm: self.run_minute_kline(m)
            if m.klines5m.last_time == tm: self.run_5_minute_kline(m)
            if m.klines15m.last_time == tm: self.run_15_minute_kline(m)
            if m.klines30m.last_time == tm: self.run_30_minute_kline(m)
            if m.klines60m.last_time == tm: self.run_60_minute_kline(m)
            if m.klines120m.last_time == tm: self.run_120_minute_kline(m)
        if refresh_flag:
            self.run_minute_kline2()  # 对齐后，全部调用一次分钟线
        if self.RealCount == 0 and refresh_flag:
            self.refresh_profit()  # 回放分支 动态计算,实盘修正
        for m in list(contracts.values()):
            m.klines1m.move_next(tm)
            m.klines5m.move_next(tm)
            m.klines15m.move_next(tm)
            m.klines30m.move_next(tm)
            m.klines60m.move_next(tm)
            m.klines120m.move_next(tm)
        return

    def real_refresh(self):
        ss = int((time.time() + 59) / 60) * 60 + 30 - time.time()
        if self.RealCount:
            logger.info(f"sleep {ss:.0f}s")
            time.sleep(ss)  # 再次进入实盘,等到每分钟的05秒
        else:  # 首次进入实盘,不等待
            user_logger.info(f"回放结束,开始实盘/模拟盘[{self.investor_id}]时间 {self.trade_time}\n")

        lcodes = []
        ncodes = []
        for m in list(self.allContract.values()):
            if m.Disable: continue  # 跳过Disable的票
            # if m.klines1m.klines == []:continue
            kls = m.load_minute_klines(m.trade_time)
            if kls:
                lcodes.append(m.codeL)
            else:
                ncodes.append(m.codeL)
        if 0 < len(ncodes) < 5:
            logger.info(f"No load {len(ncodes)} codes 1m klines {ncodes}")
        elif lcodes:
            logger.info(f"Load {len(lcodes)} codes 1m klines {lcodes[:3]}...")

        self.RealCount += 1
        self.GetAccontInfo()  # 获取实盘信息,然后跑下一分钟


# %%打印输出信号和收益，用于调试

def main_kbase():
    xj = xjclass("", 10, 1, "2023-05-01 14:00:00", "", codes=["i", "agL6"], playback=False)  # ,"au"
    xj.runallklines()


# 以下部分是示例
if __name__ == '__main__':
    from kds_util.user_logbook import user_log as logger

    os.chdir(os.path.abspath(os.path.dirname(sys.argv[0])))
    init_logs(path="./log", name="MoniHighLow", level="INFO")


    # demo
    class Param():
        def __init__(self) -> None:
            self.score = 0.0
            self.dkline = KLine()  # 当日合成的日线，动态的
            self.count1m = 0
            self.turnover = 0.0
            self.volume = 0
            self.datr5 = 0.0
            self.dma5 = 0.0
            self.ddir = 0
            self.Select = 0
            self.dir = 0
            self.kline = KLine()  # 当前分钟线
            self.mav = 0.0


    class xjclass(kBase):
        def __init__(self, investor_id="", days=150, mindays=1, stime="", etime="", codes=[], playback=False):
            super().__init__(investor_id, days, mindays, stime, etime, codes, playback)
            self.Params: Dict[str, Param] = {}
            for key in self.allContract: self.Params[key] = Param()

        def run_day_kline(self, m: Contract):  # 每个日线串行回调
            k = m.klines1d.last_kline
            p = self.Params[m.codeL]
            p.turnover = 0
            p.volume = 0
            p.datr5 = (k.high - k.low + p.datr5 * 2) / 3 if p.datr5 else k.high - k.low  # 5日波幅
            p.score = p.datr5 / k.close / m.MarginRatioByMoney if m.MarginRatioByMoney else 0
            p.dma5 = avlist([x.close for x in m.klines1d.last_n_klines(5)])  # 5日均
            p.ddir = 1 if k.close > p.dma5 else -1
            p.Select = 0
            p.dir = 0
            p.count1m = 0

        def run_day_kline2(self):  # 日线跑完，全部回调  {筛票阶段}
            for bk in self.bkinfo:
                for breed in self.bkinfo[bk]: self.Params[breed + "L6"].Select = 1  # 先全部选中，后面启用黑名单

            for bk in self.bkinfo:
                if self.Params[bk].ddir != self.Params["agL6"].ddir:
                    for breed in self.bkinfo[bk]:
                        if not self.Params.get(breed + "L6"): continue
                        self.Params[breed + "L6"].Select = 0  # 板块和文华指数不一致,屏蔽整个板块(拉黑)

            for bk in self.bkinfo:
                if self.Params[bk].ddir == self.Params["agL6"].ddir:
                    scores = sorted([(breed, self.Params[breed + "L6"].score) for breed in self.bkinfo[bk] if
                                     self.Params[breed + "L6"].Select], key=lambda x: -x[1])
                    i = 0
                    for s in scores:
                        self.Params[s[0] + "L6"].Select = 0 if i else 1  # 在没有拉黑的票中，在板块中选择杠杆最高的，获利能力最好的一个
                        i += 1

        def run30kline(self, m: Contract, k: KLine):  # 分钟线回调，计算个票{信号阶段}
            pass

        def run_minute_kline(self, m: Contract):  # 分钟线回调，计算个票{信号阶段}
            k = m.klines1m.last_kline
            p = self.Params[m.codeL]
            p.dkline.appendkline(k)  # 合成日线
            p.count1m += 1
            p.kline = k
            p.turnover += k.volume * (k.open + k.high + k.low + k.close) / 4
            p.volume += k.volume
            p.mav = p.turnover / p.volume if p.volume else k.close  # 分钟线生成的日内均线
            p.dir = 1 if k.close > p.mav else -1

        def run_minute_kline2(self):  # 每分钟跑完，回调，计算票和票、票和板块之间关系阶段，修正个票信号
            # 计算{仓位},修正状态
            for m in list(self.allContract.values()):
                if m.typ != "L6": continue
                p = self.Params[m.codeL]
                if p.Select == 0: continue  # 日线没有选中,不做
                if p.dir == p.ddir:
                    n = self.allContract[m.breed + "L9"]  # 在L9上下单(n)
                    if p.dir > 0:
                        if n.short_position > 0:
                            self.doOrder(Order(n.code, "1", '3', n.LastPrice, 1))
                        if n.long_position <= 0:
                            self.doOrder(Order(n.code, "0", '2', n.LastPrice, 1))
                    if p.dir < 0:
                        if n.long_position > 0:
                            self.doOrder(Order(n.code, "1", '2', n.LastPrice, 1))
                        if n.short_position <= 0:
                            self.doOrder(Order(n.code, "0", '3', n.LastPrice, 1))

    ################
    main_kbase()
